﻿using System;
using System.Collections.Generic;
using System.Linq;
using BFlatCompile.Nodes;
using BFlatCompile.Serializer;

namespace BFlatCompile
{
	public class MetadataSimplifier
	{
		// TODO: add "using" directives. This will simply be a list of namespaces and classes. The resolver should evaluate this list before traversing the parent chain.
		public static Dictionary<string, string> Simplify(Nodes.Program program, Compiler compiler)
		{
			// Declare various lookups.
			// Initialize a couple that can be done in one line.
			Namespace[] rootNamespaces = program.Namespaces.ToArray();
			Dictionary<string, Namespace> rootNamespaceLookup = rootNamespaces.ToDictionary<Namespace, string>(ns => ns.Name);
			Dictionary<string, Serializer.Buffer> cellsBySignature = new Dictionary<string, Serializer.Buffer>();
			Dictionary<string, int> sizeBySignature = new Dictionary<string, int>();
			Dictionary<object, object> parentLookup = new Dictionary<object, object>();
			Dictionary<string, int> startLocation = new Dictionary<string, int>();
			List<string[]> files = new List<string[]>();
			Dictionary<string, int> fileId = new Dictionary<string, int>();
			Serializer.Buffer byteCode = new Serializer.Buffer();
			Things things = new Things();

			// TODO Phase 0: optimize math - pre-evaluate constant math. "a" + "b" should turn into a "ab" constant

			// Phase 0.5: Populate raw source lookups
			Phase0d5CreateRawSourceLookup.Do(compiler, files, fileId);

			// Phase 1: get inline values
			string inlineStringTable = InlineValueSerializer.SerializeStringTable(compiler);
			string identifierTable = InlineValueSerializer.SerializeIdTable(compiler);
			string integerTable = InlineValueSerializer.SerializeIntegerTable(compiler);
			string floatTable = InlineValueSerializer.SerializeFloatTable(compiler);
			
			Dictionary<ClassInitFinalizer, Method> classInitFinalizationMarkers = new Dictionary<ClassInitFinalizer,Method>();
			// Phase 2: Add static initializer functions for each class
			Phase02AddStaticInitializers.Do(rootNamespaces, classInitFinalizationMarkers);

			// Phase 3: generate parent lookup
			Phase03PopulateParentLookup.Do(rootNamespaces, parentLookup);

			// Phase 4: Get list of namespaces and set their ID's
			Namespace[] namespaces = Phase04FlattenNamespaces.Do(rootNamespaces);
			
			// Phase 5: Get a list of all classes with subclass info resolved
			Class[] allClasses = Phase05FlattenClassesAndResolveSubclasses.Do(rootNamespaces, rootNamespaceLookup, parentLookup);

			// Phase 6: Populate AvailbleReferences for all classes
			foreach (Class c in allClasses)
			{
				ResolveAllAvailableReferences(c, parentLookup);
			}

			// Phase 7: Create a lookup of fully-qualified class names to class objects
			Dictionary<string, Class> classFullnameLookup = new Dictionary<string, Class>();
			foreach (Class c in allClasses)
			{
				classFullnameLookup[c.FullName] = c;
			}

			// Phase 8: Allocate class IDs
			int classId = 1;
			foreach (Class c in allClasses)
			{
				c.ID = classId++;
			}

			// Phase 8.5: Append class init finalizer ops now that ID's are resolved.
			foreach (ClassInitFinalizer classInitFinalizer in classInitFinalizationMarkers.Keys)
			{
				Class cls = (Class)parentLookup[classInitFinalizationMarkers[classInitFinalizer]];
				classInitFinalizer.ID = cls.ID;
			}

			// Phase 9: Resolve steps in code so all namespace and nested class references point directly to a class ID.
			foreach (Class c in allClasses)
			{
				foreach (object member in c.GetMembers(true, false, true)) // defaulting to true for 3rd param because that was the old behavior TODO: reanalyze whether it's needed here.
				{
					ResolveStepsInCode(member, c, parentLookup);
				}
			}

			// TODO: Phase 10: verify no dot derefs or variables still point to namespaces. 

			// Phase 11: get all things that can contain code. Also tag them with their 1-based ID#'s
			Phase11GetAllThingsAndSetId.Do(allClasses, things);

			// Phase 12: add headers to constructors including populating fields and calling the base class constructor
			foreach (Constructor constructor in things.Constructors)
			{
				if (!constructor.IsStatic)
				{
					constructor.AddHeaderToConstructor((Class)parentLookup[constructor], compiler);
				}
			}

			// Phase 13: serialize the code
			Phase13SerializeCode.Do(cellsBySignature, sizeBySignature, compiler, things);

			// Phase 14: concatenate serialized code and create lookup of starting locations
			Phase14ConcatenateCodeAndStartLocationLookup.Do(compiler, cellsBySignature, sizeBySignature, startLocation, byteCode);

			// Phase 15: Figure out where Program.main() is
			string mainMethodLocation = Phase15FindMain.Do(compiler, things.StaticMethods, parentLookup, startLocation);

			// Phase 16: Serialize namespace names
			string namespaceLookup = SerializeNamespaces(namespaces, parentLookup, compiler);

			// Phase 17: Serialize Method param default values (this includes both method and constructor params)
			string methodParamDefaultValuesLookup = SerializeMethodParamLookup(things.MethodParamValues, startLocation, compiler);

			// Phase 18: Serialize Constructors
			string constructorLookup = SerializeConstructorLookup(false, things.Constructors, parentLookup, startLocation, compiler);
			string staticConstructorLookup = SerializeConstructorLookup(true, things.StaticConstructors, parentLookup, startLocation, compiler);

			// Phase 19: Serialize Methods
			string methodLookup = SerializeMethodLookup(false, things.Methods, parentLookup, startLocation, compiler);
			string staticMethodLookup = SerializeMethodLookup(true, things.StaticMethods, parentLookup, startLocation, compiler);

			// Phase 20: Serialize Properties
			string propertyGetterLookup = SerializePropertiesLookup(false, true, things.PropertyGetters, parentLookup, startLocation, compiler);
			string staticPropertyGetterLookup = SerializePropertiesLookup(true, true, things.StaticPropertyGetters, parentLookup, startLocation, compiler);
			string propertySetterLookup = SerializePropertiesLookup(false, false, things.PropertySetters, parentLookup, startLocation, compiler);
			string staticPropertySetterLookup = SerializePropertiesLookup(false, false, things.StaticPropertySetters, parentLookup, startLocation, compiler);

			// Phase 21: Serialize Fields
			string fieldDefaultValueLookup = SerializeFieldsLookup(false, things.Fields, parentLookup, startLocation, compiler);
			string staticFieldDefaultValueLookup = SerializeFieldsLookup(true, things.StaticFields, parentLookup, startLocation, compiler);

			// Phase 22: Serialize Classes
			string[] classInfo = Phase22SerializeClasses.Do(allClasses, parentLookup, startLocation, compiler);
			string classBasicInfo = classInfo[0];
			string classMemberInfo = classInfo[1];
			string classBaseClasses = classInfo[2];

			// Phase 23: Create lookup for primitive methods
			string primitiveMethods = Phase23CreatePrimitiveMethodLookup.Do(compiler, rootNamespaceLookup);

			// Phase 24: Create a Raw Source Code chunked thing
			string rawSourceCode = Phase24GatherRawSource.Do(compiler, files);

			// Phase 25: Construct file chunks and return
			bool r = compiler.IsReadable;

			// Phase 26: Find special values. (System.Exception class ID, System.Exception._IRE start location)
			string specialData = Phase26FindSystemExceptionClassId.Do(compiler, allClasses, startLocation);

			// Phase 27: Serialize the StackInfo string table
			string stackInfoStringTable = Phase27SerializeStackInfoStringTable.Do(compiler);

			string byteCodeString = byteCode.GenerateStringValue(compiler);
			string tokenString = byteCode.GenerateTokenValues(compiler, fileId);

			return new Dictionary<string, string>()
			{
				{ r ? "String Table" : "t", inlineStringTable },
				{ r ? "ID Table" : "d", identifierTable },
				{ r ? "Integer Table" : "i", integerTable },
				{ r ? "Float Table" : "a", floatTable },

				{ r ? "Static Methods" : "M", staticMethodLookup },
				{ r ? "Methods" : "m", methodLookup },
				{ r ? "Static Constructors" : "X", staticConstructorLookup },
				{ r ? "Constructors" : "x", constructorLookup },
				{ r ? "Static Property Getters" : "G", staticPropertyGetterLookup },
				{ r ? "Property Getters" : "g", propertyGetterLookup },
				{ r ? "Static Property Setters" : "S", staticPropertySetterLookup },
				{ r ? "Property Setters" : "s", propertySetterLookup },
				{ r ? "Method Parameter Default Values" : "R", methodParamDefaultValuesLookup },
				{ r ? "Static Field Default Values" : "F", staticFieldDefaultValueLookup },
				{ r ? "Field Default Values" : "f", fieldDefaultValueLookup },
				{ r ? "Namespaces" : "n", namespaceLookup },
				{ r ? "Classes (Basic Info)" : "C", classBasicInfo },
				{ r ? "Classes (Members)" : "c", classMemberInfo },
				{ r ? "Classes (Base class chains)" : "H", classBaseClasses },
				{ r ? "Primitive Methods" : "h", primitiveMethods },
				{ r ? "Start Location" : "A", mainMethodLocation },
				{ r ? "Original Source Code" : "o", rawSourceCode },
				{ r ? "Token info": "T", tokenString },
				{ r ? "Byte Code" : "O", byteCodeString },
				{ r ? "Special Data" : "r", specialData  },
				{ r ? "Stack Info String Table" : "k", stackInfoStringTable },
			};
		}

		private static string SerializeFieldsLookup(bool isStatic, IList<Field> fields, Dictionary<object, object> parents, Dictionary<string, int> startLocation, Compiler compiler)
		{
			List<string> output = new List<string>();

			foreach (Field f in fields)
			{
				string signature = (f.IsStatic ? "F" : "f") + f.ID;
				Class parent = (Class)parents[f];
				int start = 0;
				if (startLocation.ContainsKey(signature))
				{
					start = startLocation[signature];
				}
				output.Add(Util.ToHex(compiler, parent.ID) + Util.GetColDelimiter(compiler) + Util.ToHex(compiler, f.NameIndex) + Util.GetColDelimiter(compiler) + Util.ToHex(compiler, start));
			}

			return string.Join(Util.GetRowDelimiter(compiler), output);
		}


		private static string SerializePropertiesLookup(bool isStatic, bool isGetter, IList<Property> properties, Dictionary<object, object> parents, Dictionary<string, int> startLocation, Compiler compiler)
		{
			List<string> output = new List<string>();
			foreach (Property property in properties)
			{
				string signature;
				if (isGetter)
				{
					signature = (isStatic ? "G" : "g") + property.GetterID;
				}
				else
				{
					signature = (isStatic ? "S" : "s") + property.SetterID;
				}
				Class c = (Class)parents[property];
				string row = Util.ToHex(compiler, c.ID) + Util.GetColDelimiter(compiler) + Util.ToHex(compiler, property.NameIndex) + Util.GetColDelimiter(compiler) + Util.ToHex(compiler, startLocation[signature]);
				output.Add(row);
			}
			return string.Join(Util.GetRowDelimiter(compiler), output);
		}

		private static string SerializeMethodLookup(bool isStatic, IList<Method> methods, Dictionary<object, object> parents, Dictionary<string, int> startLocation, Compiler compiler)
		{
			List<string> output = new List<string>();

			foreach (Method method in methods)
			{
				string signature = (isStatic ? "M" : "m") + method.ID;
				Class c = (Class)parents[method];

				List<string> row = new List<string>();

				row.Add(Util.ToHex(compiler, c.ID));
				row.Add(Util.ToHex(compiler, method.NameIndex));
				row.Add(Util.ToHex(compiler, startLocation[signature]));

				foreach (MethodParam p in method.Params)
				{
					row.Add(Util.ToHex(compiler, p.NameIndex));
					row.Add(Util.ToHex(compiler, p.ID));
				}
				output.Add(string.Join(Util.GetColDelimiter(compiler), row));
			}
			return string.Join(Util.GetRowDelimiter(compiler), output);
		}

		private static string SerializeConstructorLookup(bool isStatic, IList<Constructor> constructors, Dictionary<object, object> parents, Dictionary<string, int> startLocation, Compiler compiler)
		{
			List<string> output = new List<string>();

			foreach (Constructor constructor in constructors)
			{
				List<string> row = new List<string>();

				string signature = (isStatic ? "X" : "x") + constructor.ID;
				Class c = (Class)parents[constructor];

				row.Add(Util.ToHex(compiler, c.ID));
				row.Add(Util.ToHex(compiler, startLocation[signature]));
				row.Add(Util.ToHex(compiler, constructor.Params.Length));

				foreach (MethodParam p in constructor.Params)
				{
					row.Add(Util.ToHex(compiler, p.NameIndex));
					row.Add(Util.ToHex(compiler, p.ID));

				}
				output.Add(string.Join(Util.GetColDelimiter(compiler), row));
			}

			return string.Join(Util.GetRowDelimiter(compiler), output);
		}

		private static string SerializeMethodParamLookup(IList<MethodParam> methodParams, Dictionary<string, int> startLocation, Compiler compiler)
		{
			// Just Code location
			List<string> output = new List<string>();
			string zero = Util.ToHex(compiler, 0);
			foreach (MethodParam p in methodParams)
			{
				int id = p.ID;
				if (id > 0)
				{
					string signature = "R" + id;
					string location = Util.ToHex(compiler, startLocation[signature]);
					while (output.Count < id)
					{
						output.Add(zero);
					}

					output[id - 1] = location;
				}
			}
			return string.Join(Util.GetRowDelimiter(compiler), output);
		}

		private static string SerializeNamespaces(IList<Namespace> namespaces, Dictionary<object, object> parents, Compiler compiler)
		{
			/*
			 * Name ID , Parent Namespace or 0
			 */
			List<string> output = new List<string>();
			foreach (Namespace ns in namespaces)
			{
				output.Add(
					Util.ToHex(compiler, ns.NameIndex) + Util.GetColDelimiter(compiler) +
					Util.ToHex(compiler, parents[ns] == null ? 0 : ((Namespace)parents[ns]).ID)
					);
			}

			return string.Join(Util.GetRowDelimiter(compiler), output);
		}

		private static void ResolveAllAvailableReferences(Class c, Dictionary<object, object> parents)
		{
			// Find all parent-less objects. These are root namespaces. These are ALWAYS available.
			foreach (object child in parents.Keys)
			{
				object parent = parents[child];
				if (parent == null)
				{
					Namespace ns = (Namespace)child;
					c.AvailableReferences[ns.Name] = child;
				}
			}

			// GetMembers orders members from base classes first
			foreach (object member in c.GetMembers(true, true, false))
			{
				if (member is Field) { c.AvailableReferences[((Field)member).Name] = member; }
				else if (member is Property) { c.AvailableReferences[((Property)member).Name] = member; }
				else if (member is Method) { c.AvailableReferences[((Method)member).Name] = member; }
				else if (member is Class) { c.AvailableReferences[((Class)member).Name] = member; }
				else if (member is Constructor) { }
				else
				{
					throw new Exception("This should not happen.");
				}
			}

			// TODO: should probably prevent creating a member
			// with the same name as parent.

			// should be able to refer to a class from within itself
			c.AvailableReferences[c.Name] = c;

			// all DIRECT children of namespaces in this class's lineage chain should be availbe
			object parentChainWalker = parents[c];
			while (parentChainWalker != null)
			{
				if (parentChainWalker is Class)
				{
					c.AvailableReferences[((Class)parentChainWalker).Name] = parentChainWalker;
				}
				else // is namespace
				{
					Namespace ns = (Namespace)parentChainWalker;
					foreach (Class nsc in ns.Classes)
					{
						c.AvailableReferences[nsc.Name] = nsc;
					}
					foreach (Namespace nsn in ns.Namespaces)
					{
						c.AvailableReferences[nsn.Name] = nsn;
					}
				}

				parentChainWalker = parents[parentChainWalker];
			}
		}

		private static void ResolveStepsInCode(object member, Class c, Dictionary<object, object> parents)
		{
			if (member is ICodeContainer)
			{
				((ICodeContainer)member).ResolveCode(c, parents);
			}
		}
	}
}
